/* jSSC (Java Simple Serial Connector) - serial port communication library.
 * © Alexey Sokolov (scream3r), 2010.
 *
 * This file is part of jSSC.
 *
 * jSSC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jSSC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jSSC.  If not, see <http://www.gnu.org/licenses/>.
 *
 * If you use jSSC in public project you can inform me about this by e-mail,
 * of course if you want it.
 *
 * e-mail: scream3r.org@gmail.com
 * web-site: www.scream3r.org
 */
#include <jni.h>
#include <stdlib.h>
#include <windows.h>
#include "jssc_SerialNativeInterface.h"

/* 
 * Открытие порта.
 */
JNIEXPORT jint JNICALL Java_jssc_SerialNativeInterface_openPort(JNIEnv *env, jobject object, jstring portName){
    char prefix[] = "\\\\.\\";
    const char* port = env->GetStringUTFChars(portName, JNI_FALSE);
    strcat(prefix, port);
    HANDLE hComm;
    hComm = CreateFile(prefix,
                       GENERIC_READ | GENERIC_WRITE,
                       0,
                       0,
                       OPEN_EXISTING,
                       FILE_FLAG_OVERLAPPED,
                       0);
    env->ReleaseStringUTFChars(portName, port);
    return (jint)hComm;
};

/*
 * Установка параметров порта.
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_setParams
  (JNIEnv *env, jobject object, jint portHandle, jint baudRate, jint byteSize, jint stopBits, jint parity){
    HANDLE hComm = (HANDLE)portHandle;
    DCB *dcb = new DCB();
    jboolean returnValue = JNI_FALSE;
    if(GetCommState(hComm, dcb)){
        dcb->BaudRate = baudRate;
        dcb->ByteSize = byteSize;
        dcb->StopBits = stopBits;
        dcb->Parity = parity;
        //dcb->fRtsControl = RTS_CONTROL_ENABLE;
        //dcb->fDtrControl = DTR_CONTROL_ENABLE;
        if(SetCommState(hComm, dcb)){
            returnValue = JNI_TRUE;
        }
    }
    delete dcb;
    return returnValue;
}

/*
 * PurgeComm
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_purgePort
  (JNIEnv *env, jobject object, jint portHandle, jint flags){
    HANDLE hComm = (HANDLE)portHandle;
    DWORD dwFlags = (DWORD)flags;
    return (PurgeComm(hComm, dwFlags) ? JNI_TRUE : JNI_FALSE);
}

/*
 * Закрытие порта.
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_closePort
  (JNIEnv *env, jobject object, jint portHandle){
    HANDLE hComm = (HANDLE)portHandle;
    return (CloseHandle(hComm) ? JNI_TRUE : JNI_FALSE);
}

/*
 * Установка маски ивентов.
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_setEventsMask
  (JNIEnv *env, jobject object, jint portHandle, jint mask){
    HANDLE hComm = (HANDLE)portHandle;
    DWORD dwEvtMask = (DWORD)mask;
    return (SetCommMask(hComm, dwEvtMask) ? JNI_TRUE : JNI_FALSE);
}

/*
 * Получение маски ивентов в виде переменной типа int
 */
JNIEXPORT jint JNICALL Java_jssc_SerialNativeInterface_getEventsMask
  (JNIEnv *env, jobject object, jint portHandle){
    HANDLE hComm = (HANDLE)portHandle;
    DWORD lpEvtMask;
    if(GetCommMask(hComm, &lpEvtMask)){
        return (jint)lpEvtMask;
    }
    else {
        return -1;
    }
}

/*
 * Включение и выключение линии RTS.
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_setRTS
  (JNIEnv *env, jobject object, jint portHandle, jboolean enabled){
    HANDLE hComm = (HANDLE)portHandle;
    if(enabled == JNI_TRUE){
        return (EscapeCommFunction(hComm, SETRTS) ? JNI_TRUE : JNI_FALSE);
    }
    else {
        return (EscapeCommFunction(hComm, CLRRTS) ? JNI_TRUE : JNI_FALSE);
    }
}

/*
 * Включение и выключение линии DTR.
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_setDTR
  (JNIEnv *env, jobject object, jint portHandle, jboolean enabled){
    HANDLE hComm = (HANDLE)portHandle;
    if(enabled == JNI_TRUE){
        return (EscapeCommFunction(hComm, SETDTR) ? JNI_TRUE : JNI_FALSE);
    }
    else {
        return (EscapeCommFunction(hComm, CLRDTR) ? JNI_TRUE : JNI_FALSE);
    }
}

/*
 * Запись данных в порт
 * portHandle - хендл порта для которого производится запись.
 * buffer - массив байт, который необходимо записать.
 */
JNIEXPORT jboolean JNICALL Java_jssc_SerialNativeInterface_writeBytes
  (JNIEnv *env, jobject object, jint portHandle, jbyteArray buffer){
    HANDLE hComm = (HANDLE)portHandle;
    DWORD lpNumberOfBytesTransferred;
    DWORD lpNumberOfBytesWritten;
    OVERLAPPED *overlapped = new OVERLAPPED();
    jboolean returnValue = JNI_FALSE;
    jbyte* jBuffer = env->GetByteArrayElements(buffer, JNI_FALSE);
    overlapped->hEvent = CreateEventA(NULL, true, false, NULL);
    if(WriteFile(hComm, jBuffer, (DWORD)env->GetArrayLength(buffer), &lpNumberOfBytesWritten, overlapped)){
        returnValue = JNI_TRUE;
    }
    else if(GetLastError() == ERROR_IO_PENDING){
        if(WaitForSingleObject(overlapped->hEvent, INFINITE) == WAIT_OBJECT_0){
            if(GetOverlappedResult(hComm, overlapped, &lpNumberOfBytesTransferred, false)){
                returnValue = JNI_TRUE;
            }
        }
    }
    env->ReleaseByteArrayElements(buffer, jBuffer, 0);
    CloseHandle(overlapped->hEvent);
    delete overlapped;
    return returnValue;
}

/*
 * Чтение данных из порта
 * portHandle - хендл порта для которого производится чтение.
 * byteCount - количество байт, которое необходимо прочитать.
 */
JNIEXPORT jbyteArray JNICALL Java_jssc_SerialNativeInterface_readBytes
  (JNIEnv *env, jobject object, jint portHandle, jint byteCount){
    HANDLE hComm = (HANDLE)portHandle;
    DWORD lpNumberOfBytesTransferred;
    DWORD lpNumberOfBytesRead;
    OVERLAPPED *overlapped = new OVERLAPPED();
    jbyte lpBuffer[byteCount];
    jbyteArray returnArray = env->NewByteArray(byteCount);
    overlapped->hEvent = CreateEventA(NULL, true, false, NULL);
    if(ReadFile(hComm, lpBuffer, (DWORD)byteCount, &lpNumberOfBytesRead, overlapped)){
        env->SetByteArrayRegion(returnArray, 0, byteCount, lpBuffer);
    }
    else if(GetLastError() == ERROR_IO_PENDING){
        if(WaitForSingleObject(overlapped->hEvent, INFINITE) == WAIT_OBJECT_0){
            if(GetOverlappedResult(hComm, overlapped, &lpNumberOfBytesTransferred, false)){
                env->SetByteArrayRegion(returnArray, 0, byteCount, lpBuffer);
            }
        }
    }
    CloseHandle(overlapped->hEvent);
    delete overlapped;
    return returnArray;
}

/*
 * Ожидание ивента по выставленной маске
 * portHandle - хендл порта для которого следует ожидать ивент.
 */
JNIEXPORT jobjectArray JNICALL Java_jssc_SerialNativeInterface_waitEvents
  (JNIEnv *env, jobject object, jint portHandle) {
    HANDLE hComm = (HANDLE)portHandle;
    DWORD lpEvtMask = 0;
    DWORD lpNumberOfBytesTransferred = 0;
    OVERLAPPED *overlapped = new OVERLAPPED();
    jclass intClass = env->FindClass("[I");
    jobjectArray returnArray;
    boolean functionSuccessful = false;
    overlapped->hEvent = CreateEventA(NULL, true, false, NULL);
    if(WaitCommEvent(hComm, &lpEvtMask, overlapped)){
        functionSuccessful = true;
    }
    else if(GetLastError() == ERROR_IO_PENDING){
        if(WaitForSingleObject(overlapped->hEvent, INFINITE) == WAIT_OBJECT_0){
            if(GetOverlappedResult(hComm, overlapped, &lpNumberOfBytesTransferred, false)){
                functionSuccessful = true;
            }
        }
    }
    if(functionSuccessful){
        boolean executeGetCommModemStatus = false;
        boolean executeClearCommError = false;
        DWORD events[8];
        jint eventsCount = 0;
        if((EV_BREAK & lpEvtMask) == EV_BREAK){
            events[eventsCount] = EV_BREAK;
            eventsCount++;
        }
        if((EV_CTS & lpEvtMask) == EV_CTS){
            events[eventsCount] = EV_CTS;
            eventsCount++;
            executeGetCommModemStatus = true;
        }
        if((EV_DSR & lpEvtMask) == EV_DSR){
            events[eventsCount] = EV_DSR;
            eventsCount++;
            executeGetCommModemStatus = true;
        }
        if((EV_ERR & lpEvtMask) == EV_ERR){
            events[eventsCount] = EV_ERR;
            eventsCount++;
            executeClearCommError = true;
        }
        if((EV_RING & lpEvtMask) == EV_RING){
            events[eventsCount] = EV_RING;
            eventsCount++;
            executeGetCommModemStatus = true;
        }
        if((EV_RLSD & lpEvtMask) == EV_RLSD){
            events[eventsCount] = EV_RLSD;
            eventsCount++;
            executeGetCommModemStatus = true;
        }
        if((EV_RXCHAR & lpEvtMask) == EV_RXCHAR){
            events[eventsCount] = EV_RXCHAR;
            eventsCount++;
            executeClearCommError = true;
        }
        if((EV_RXFLAG & lpEvtMask) == EV_RXFLAG){
            events[eventsCount] = EV_RXFLAG;
            eventsCount++;
            executeClearCommError = true;
        }
        if((EV_TXEMPTY & lpEvtMask) == EV_TXEMPTY){
            events[eventsCount] = EV_TXEMPTY;
            eventsCount++;
            executeClearCommError = true;
        }
        /*
         * При необходимости вызываем функцию GetCommModemStatus
         * и получем статусы линий
         */
        jint statusCTS = 0;
        jint statusDSR = 0;
        jint statusRING = 0;
        jint statusRLSD = 0;
        boolean successGetCommModemStatus = false;
        if(executeGetCommModemStatus){
            DWORD lpModemStat;
            if(GetCommModemStatus(hComm, &lpModemStat)){
                successGetCommModemStatus = true;
                if((MS_CTS_ON & lpModemStat) == MS_CTS_ON){
                    statusCTS = 1;
                }
                if((MS_DSR_ON & lpModemStat) == MS_DSR_ON){
                    statusDSR = 1;
                }
                if((MS_RING_ON & lpModemStat) == MS_RING_ON){
                    statusRING = 1;
                }
                if((MS_RLSD_ON & lpModemStat) == MS_RLSD_ON){
                    statusRLSD = 1;
                }
            }
            else {
                jint lastError = (jint)GetLastError();
                statusCTS = lastError;
                statusDSR = lastError;
                statusRING = lastError;
                statusRLSD = lastError;
            }
        }
        /*
         * При необходимости вызываем функцию ClearCommError
         * и получаем информацию о количестве байт во входном и выходном буферах,
         * а также информацию об ошибках.
         */
        jint bytesCountIn = 0;
        jint bytesCountOut = 0;
        jint communicationsErrors = 0;
        boolean successClearCommError = false;
        if(executeClearCommError){
            DWORD lpErrors;
            COMSTAT *comstat = new COMSTAT();
            if(ClearCommError(hComm, &lpErrors, comstat)){
                successClearCommError = true;
                bytesCountIn = (jint)comstat->cbInQue;
                bytesCountOut = (jint)comstat->cbOutQue;
                communicationsErrors = (jint)lpErrors;
            }
            else {
                jint lastError = (jint)GetLastError();
                bytesCountIn = lastError;
                bytesCountOut = lastError;
                communicationsErrors = lastError;
            }
            delete comstat;
        }
        /*
         * Создаём массив int[][] который впоследствии будем возвращать
         * на основании полученной размерности "eventsCount".
         */
        returnArray = env->NewObjectArray(eventsCount, intClass, NULL);
        /*
         * Заполняем массив ивентов соответствующими значениями.
         */
        for(jint i = 0; i < eventsCount; i++){
            jint returnValues[2];
            switch(events[i]){
                case EV_BREAK:
                    returnValues[0] = (jint)events[i];
                    returnValues[1] = 0;
                    goto forEnd;
                case EV_CTS:
                    returnValues[1] = statusCTS;
                    goto modemStatus;
                case EV_DSR:
                    returnValues[1] = statusDSR;
                    goto modemStatus;
                case EV_ERR:
                    returnValues[1] = communicationsErrors;
                    goto bytesAndErrors;
                case EV_RING:
                    returnValues[1] = statusRING;
                    goto modemStatus;
                case EV_RLSD:
                    returnValues[1] = statusRLSD;
                    goto modemStatus;
                case EV_RXCHAR:
                    returnValues[1] = bytesCountIn;
                    goto bytesAndErrors;
                case EV_RXFLAG:
                    returnValues[1] = bytesCountIn;
                    goto bytesAndErrors;
                case EV_TXEMPTY:
                    returnValues[1] = bytesCountOut;
                    goto bytesAndErrors;
                default:
                    returnValues[0] = (jint)events[i];
                    returnValues[1] = 0;
                    goto forEnd;
            };
            modemStatus: {
                if(successGetCommModemStatus){
                    returnValues[0] = (jint)events[i];
                }
                else {
                    returnValues[0] = -1;
                }
                goto forEnd;
            }
            bytesAndErrors: {
                if(successClearCommError){
                    returnValues[0] = (jint)events[i];
                }
                else {
                    returnValues[0] = -1;
                }
                goto forEnd;
            }
            forEnd: {
                jintArray singleResultArray = env->NewIntArray(2);
                env->SetIntArrayRegion(singleResultArray, 0, 2, returnValues);
                env->SetObjectArrayElement(returnArray, i, singleResultArray);
            };
        }
    }
    else {
        returnArray = env->NewObjectArray(1, intClass, NULL);
        jint returnValues[2];
        returnValues[0] = -1;
        returnValues[1] = (jint)GetLastError();
        jintArray singleResultArray = env->NewIntArray(2);
        env->SetIntArrayRegion(singleResultArray, 0, 2, returnValues);
        env->SetObjectArrayElement(returnArray, 0, singleResultArray);
    };
    CloseHandle(overlapped->hEvent);
    delete overlapped;
    return returnArray;
}

/*
 * Получение списка портов в виде строкового массива.
 */
JNIEXPORT jobjectArray JNICALL Java_jssc_SerialNativeInterface_getSerialPortNames
  (JNIEnv *env, jobject object){
    HKEY phkResult;
    LPCSTR lpSubKey = "HARDWARE\\DEVICEMAP\\SERIALCOMM\\";
    jobjectArray returnArray;
    if(RegOpenKeyExA(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &phkResult) == ERROR_SUCCESS){
        boolean hasMoreElements = true;
        DWORD keysCount = 0;
        char valueName[256];
        DWORD valueNameSize;
        DWORD enumResult;
        while(hasMoreElements){
            valueNameSize = 256;
            enumResult = RegEnumValueA(phkResult, keysCount, valueName, &valueNameSize, NULL, NULL, NULL, NULL);
            if(enumResult == ERROR_SUCCESS){
                keysCount++;
            }
            else if(enumResult == ERROR_NO_MORE_ITEMS){
                hasMoreElements = false;
            }
            else {
                hasMoreElements = false;
            }
        }
        if(keysCount > 0){
            jclass stringClass = env->FindClass("java/lang/String");
            returnArray = env->NewObjectArray((jsize)keysCount, stringClass, NULL);
            char lpValueName[256];
            DWORD lpcchValueName;
            byte lpData[256];
            DWORD lpcbData;
            DWORD result;
            for(DWORD i = 0; i < keysCount; i++){
                lpcchValueName = 256;
                lpcbData = 256;
                result = RegEnumValueA(phkResult, i, lpValueName, &lpcchValueName, NULL, NULL, lpData, &lpcbData);
                if(result == ERROR_SUCCESS){
                    env->SetObjectArrayElement(returnArray, i, env->NewStringUTF((char*)lpData));
                }
            }
        }
        CloseHandle(phkResult);
    }
    return returnArray;
}

/*
 * returnValues[0] - CTS
 * returnValues[1] - DSR
 * returnValues[2] - RING
 * returnValues[3] - RLSD
 * 
 */
JNIEXPORT jintArray JNICALL Java_jssc_SerialNativeInterface_getLinesStatus
  (JNIEnv *env, jobject object, jint portHandle){
    HANDLE hComm = (HANDLE)portHandle;
    DWORD lpModemStat;
    jint returnValues[4];
    for(jint i = 0; i < 4; i++){
        returnValues[i] = 0;
    }
    jintArray returnArray = env->NewIntArray(4);
    if(GetCommModemStatus(hComm, &lpModemStat)){
        if((MS_CTS_ON & lpModemStat) == MS_CTS_ON){
            returnValues[0] = 1;
        }
        if((MS_DSR_ON & lpModemStat) == MS_DSR_ON){
            returnValues[1] = 1;
        }
        if((MS_RING_ON & lpModemStat) == MS_RING_ON){
            returnValues[2] = 1;
        }
        if((MS_RLSD_ON & lpModemStat) == MS_RLSD_ON){
            returnValues[3] = 1;
        }
    }
    env->SetIntArrayRegion(returnArray, 0, 4, returnValues);
    return returnArray;
}
